在 NestJS 中,Service 本身是单例模式(默认),而 Request 对象是请求级别的上下文,因此直接在 Service 中获取 req 需要结合「作用域」「显式传递」或「异步本地存储(ALS)」实现。以下是 3 种常用方案,按「推荐优先级」排序:
方案 1:控制器显式传递 req(最推荐,解耦)
这是最符合 NestJS 设计理念的方式:控制器层获取 req,调用 Service 时作为参数传递。Service 保持单例,不耦合请求上下文,通用性强。
实现步骤:
- 控制器中通过
@Req()装饰器获取请求对象; - 调用 Service 方法时将 req 作为参数传入。
typescript
运行
typescript
// user.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express'; // 若用 Fastify 则导入 fastify 的 Request
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async getCurrentUser(@Req() req: Request) {
// 控制器获取 req,传递给 Service
return this.userService.getCurrentUser(req);
}
}
// user.service.ts
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
@Injectable() // 保持默认单例,无性能开销
export class UserService {
getCurrentUser(req: Request) {
// 直接使用传递过来的 req
const userId = req.user?.id; // 示例:获取用户 ID
const ip = req.ip; // 示例:获取客户端 IP
return { userId, ip };
}
}方案 2:注入 REQUEST 作用域(简单但有性能开销)
通过将 Service 的作用域设置为 Scope.REQUEST,并注入 REQUEST 令牌,直接在 Service 中获取 req。
⚠️ 注意:REQUEST 作用域的 Service 每次请求都会创建新实例,频繁使用会增加性能开销,仅适合简单场景。
实现步骤:
- 导入
REQUEST、Inject、Scope; - 装饰器中设置
scope: Scope.REQUEST; - 构造函数中注入
REQUEST对象。
typescript
运行
typescript
// user.service.ts
import { Injectable, Inject, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST }) // 设置为请求作用域
export class UserService {
// 注入 REQUEST 对象
constructor(@Inject(REQUEST) private readonly request: Request) {}
getCurrentUser() {
// 直接使用 this.request
const userId = this.request.user?.id;
const ip = this.request.ip;
return { userId, ip };
}
}
// 控制器无需传参,直接调用
// user.controller.ts
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async getCurrentUser() {
return this.userService.getCurrentUser();
}
}方案 3:AsyncLocalStorage(复杂场景无耦合)
对于多层级 Service 调用(如 Service A → Service B → Service C 都需要 req),显式传递参数会很繁琐,此时可使用 Node.js 的 AsyncLocalStorage(ALS)实现「请求上下文共享」。
实现步骤:
1. 创建请求上下文工具类
typescript
运行
typescript
// src/common/request-context.ts
import { AsyncLocalStorage } from 'async_hooks';
import { Request } from 'express';
// 创建 ALS 实例,存储 Request 对象
const asyncLocalStorage = new AsyncLocalStorage<Request>();
// 启动请求上下文(在中间件中调用)
export const runInRequestContext = (req: Request, callback: () => void) => {
asyncLocalStorage.run(req, callback);
};
// 获取当前请求的 req 对象
export const getRequestContext = (): Request => {
const req = asyncLocalStorage.getStore();
if (!req) {
throw new Error('请求上下文未初始化,请确保中间件已生效');
}
return req;
};2. 创建全局中间件,初始化上下文
typescript
运行
typescript
// src/middleware/request-context.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { runInRequestContext } from '../common/request-context';
@Injectable()
export class RequestContextMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// 将 req 存入 ALS,并执行后续逻辑
runInRequestContext(req, next);
}
}3. 注册全局中间件
typescript
运行
typescript
// src/app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { RequestContextMiddleware } from './middleware/request-context.middleware';
@Module({
// ... 其他配置
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 全局注册中间件
consumer.apply(RequestContextMiddleware).forRoutes('*');
}
}4. Service 中获取 req(无需传参、无作用域限制)
typescript
运行
typescript
// user.service.ts
import { Injectable } from '@nestjs/common';
import { getRequestContext } from '../common/request-context';
@Injectable() // 保持单例,无性能开销
export class UserService {
getCurrentUser() {
// 从 ALS 中获取 req
const req = getRequestContext();
const userId = req.user?.id;
const ip = req.ip;
return { userId, ip };
}
}
// 其他层级的 Service 也可直接调用 getRequestContext()
// order.service.ts
@Injectable()
export class OrderService {
constructor(private readonly userService: UserService) {}
getOrderUser() {
const req = getRequestContext(); // 直接获取 req
return { orderId: 123, userId: req.user?.id };
}
}总结
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 控制器显式传递 | 解耦、性能好、易维护 | 多层调用需传参 | 大多数常规场景 |
| REQUEST 作用域 | 实现简单、无需传参 | 性能开销、Service 非单例 | 简单场景(仅单个 Service 使用) |
| AsyncLocalStorage | 无耦合、多层调用便捷 | 配置稍复杂 | 复杂层级调用场景 |
最佳实践:优先使用「控制器显式传递」;简单场景可使用「REQUEST 作用域」;复杂多层调用用「AsyncLocalStorage」。尽量避免 Service 直接依赖 req,仅在必要时获取(如 IP、用户信息等)。
